#include "LFilePack.h"




bool CLFilePack::__insert_file( const wchar_t* name, const wchar_t* full_name , lfp_data_type type, int compress_rate )
{
	if ( NULL == full_name )
		return false;
	return __insert_data( name, full_name, type, compress_rate);
}


bool CLFilePack::__insert_buffer( const wchar_t* name, int compress_rate )
{
		return __insert_data( name, NULL, lfpdt_dataBuffer, compress_rate);
}


bool CLFilePack::__insert_data( const wchar_t* name,  const wchar_t* full_name, lfp_data_type type, int compress_rate )
{
	if ( NULL == full_name && lfpdt_dataBuffer != type )
		return false;
	size_t nSize = m_listDataEntry.size();
	if(nSize >= m_FileHeader.m_nHashTableSize)
	{
		__resize_hash_table((int)m_listDataEntry.size()*2);
	}
	sHashValue hashValue;
	CLHashDecryptTable::singleton().string_hash(name,hashValue);
	//calculate the item index in the hash table;
	int hashTableIndex = hashValue.indexHash % m_FileHeader.m_nHashTableSize;
	for(unsigned int i = 0 ; i < m_FileHeader.m_nHashTableSize ; ++i)
	{
		int nStartAddr = 0;
		if ( 0 != i )
		{
			nStartAddr = m_listDataEntry[i-1].m_DataEntryInfo.m_nStart + m_listDataEntry[i-1].m_DataEntryInfo.m_nLength;
		}
		int index = (hashTableIndex + i)%m_FileHeader.m_nHashTableSize;
		if(m_pHashTable[index].m_Flag == HASHFLAG_NOT_USED )
		{
			int iDataItem = (int) m_listDataEntry.size();
			sFPDataEntry newentry;
			m_listDataEntry.push_back(newentry);

			sFPDataEntry& entry = m_listDataEntry[m_listDataEntry.size() - 1];

			m_pHashTable[index].m_HashIndex  = hashValue.indexHash ;
			m_pHashTable[index].m_HashValue1 = hashValue.valueA;
			m_pHashTable[index].m_HashValue2 = hashValue.valueB;
			m_pHashTable[index].m_FPIndex    = iDataItem;
			m_pHashTable[index].m_Flag       = HASHFLAG_COMMON;
			entry.m_DataEntryInfo.m_nHashTableIndex = index;
			entry.m_DataEntryInfo.m_nStart = nStartAddr;
			entry.m_DataEntryInfo.m_nCompressRate = compress_rate;
			switch(type)
			{
			case lfpdt_file:
				{
					wcscpy(entry.m_full_file_name, full_name);
					entry.m_DataEntryInfo.m_nLength = getFileLen(full_name);
					entry.m_DataEntryInfo.m_nOriginLength = entry.m_DataEntryInfo.m_nLength;
					wstring strName = L"";
					if ( NULL == name )
					{
						strName = getFileName(full_name);
						wcscpy(entry.m_DataEntryInfo.m_name, strName.c_str());
					}
					else
						wcscpy(entry.m_DataEntryInfo.m_name, name);


				}
				break;
			case lfpdt_dir:
				{
					if ( NULL == name )
					{
						/// wait 
					}
					else
						wcscpy(entry.m_DataEntryInfo.m_name, name);

					entry.m_DataEntryInfo.m_nLength = 0;
				}
				break;
			case lfpdt_dataBuffer:
				{
					entry.m_DataEntryInfo.m_nLength = 0;

				}
				break;
			default:
				break;
			}
			return true;
		}
	}
	return false;
}




bool CLFilePack::_unpack_data(const wchar_t* file_name, sFPDataEntryInfo& entryInfo )
{
	if ( NULL == file_name )
		return false;

	CLFPInStream* pInStream = __unwrapBuffer(entryInfo);
	if ( NULL == pInStream )
		return false;
	bool bRet =  _write_data_to_file((const char*)pInStream->getUnCompressData(), file_name, pInStream->getOriginLength());
	close_stream( pInStream );
	delete pInStream;
	pInStream = NULL;
	return bRet;
}

bool CLFilePack::__open_for_read(const wchar_t* file_name)
{
	if ( NULL == file_name )
		return false;
	m_readStream.open(file_name , ios::binary);
	if(m_readStream.fail())
		return false;
	return __open_for_read(m_readStream);
}

bool CLFilePack::__open_for_read(ifstream & stream)
{
	//// read file header
	stream.read((char*)&m_FileHeader,sizeof(sFPFileHeader));
	if ( __check_file_header() == false )
	{
		m_eState = elfp_open_failed;
		return false;
	}
	/// read hash table 
	//stream.seekg( sizeof(sFPFileHeader),ios::beg  );
	m_pHashTable = new sFPHashTableItem[m_FileHeader.m_nHashTableSize];
	
	stream.read((char*)m_pHashTable,m_FileHeader.m_nHashTableSize*sizeof(sFPHashTableItem));

//#ifdef _DEBUG
//	for ( unsigned long i=0; i<m_FileHeader.m_nHashTableSize; i++ )
//	{
//
//		if ( HASHFLAG_COMMON == m_pHashTable[i].m_Flag )
//		{
//			printf("%d  %d  %d\n", m_pHashTable[i].m_HashIndex, m_pHashTable[i].m_HashValue1, m_pHashTable[i].m_HashValue2);
//		}
//	}
//#endif

	/// 
	if ( stream.eof() )
		return false;
	/// read data entry table
	size_t entryTableSize = m_FileHeader.m_nFpTableSize;
	m_listDataEntry.reserve(entryTableSize);
	m_listDataEntry.resize( entryTableSize );
	for ( size_t i=0; i<m_listDataEntry.size(); i++ )
	{
		sFPDataEntry& entry = m_listDataEntry[i];

		stream.read((char*)&entry.m_DataEntryInfo, sizeof(sFPDataEntryInfo));

	}
	m_eState = elfp_ok;
	return true;

}


bool CLFilePack::_fill_header()
{
	m_FileHeader.m_nFpTableSize = m_listDataEntry.size();
	return true;
}

bool CLFilePack::_write_data_to_stream(ostream& stream)
{
	//int nStart = __calDataEntryPoint();
	stream.write((const char*)&m_FileHeader,sizeof(sFPFileHeader));

	stream.write((const char*)m_pHashTable, m_FileHeader.m_nHashTableSize*sizeof(sFPHashTableItem));

	

	int nDataStart = __getDataStart();
	stream.seekp( nDataStart, ios::beg );
	for ( size_t i=0; i<m_listDataEntry.size(); i++ )
	{
		sFPDataEntry& entry = m_listDataEntry[i];
		if ( entry.m_DataEntryInfo.m_nLength >0 )
		{
			if ( entry.m_bBuffer )
			{

			}
			else
			{
				entry.m_InStream.m_nOriginLength = getFileBuffer(entry.m_full_file_name, &entry.m_InStream.m_pData );
				int nRet = entry.m_InStream.compressData();
				entry.m_DataEntryInfo.m_nLength = entry.m_InStream.getLength();
				entry.m_DataEntryInfo.m_nOriginLength = entry.m_InStream.m_nOriginLength;
				stream.write( (const char*)entry.m_InStream.getData(), entry.m_InStream.getLength());
				close_stream( &entry.m_InStream );
			}

		}
	}
	__calDataEntryPoint();
	int nEntryListStart = __getDataEntryListStart();
	stream.seekp( nEntryListStart, ios::beg );
	for ( size_t i=0; i<m_listDataEntry.size(); i++ )
	{
		sFPDataEntry& entry = m_listDataEntry[i];
		stream.write((const char*)&entry.m_DataEntryInfo,sizeof(sFPDataEntryInfo));

	}

	return true;

}


/// hashtable
void CLFilePack::__init_hash_table(int hash_table_size)
{
		/*
	Alloc the hash talbe buffer;
	*/
	if(m_pHashTable != NULL)
	{
		delete [] m_pHashTable; 
		m_pHashTable = NULL;
	}
	m_pHashTable = new sFPHashTableItem [hash_table_size];
	m_FileHeader.m_nHashTableSize = hash_table_size;
	__clear_hash_table();
}


void CLFilePack::__clear_hash_table()
{
		/*
	Clear the whole hash table.
	*/
	assert(m_pHashTable);
	int nHashTable = m_FileHeader.m_nHashTableSize;
	for(int i = 0 ; i < nHashTable ; ++ i)
	{
		m_pHashTable[i].m_FPIndex    = -1;
		m_pHashTable[i].m_Flag       = HASHFLAG_NOT_USED;//Not used
		m_pHashTable[i].m_HashIndex  = -1;
		m_pHashTable[i].m_HashValue1 = -1;
		m_pHashTable[i].m_HashValue2 = -1;
	}  
}

void CLFilePack::__resize_hash_table(int hash_table_size)
{
	__init_hash_table(hash_table_size);
	__recalculate_hash_table();
}

void CLFilePack::__recalculate_hash_table()
{
	sHashValue hashValue;
	size_t nDataItem = m_listDataEntry.size();
	for(size_t iDataItem = 0 ; iDataItem < nDataItem ; ++ iDataItem)
	{
		sFPDataEntry& entry = m_listDataEntry[iDataItem];

		const wchar_t* name = entry.m_DataEntryInfo.m_name;
		CLHashDecryptTable::singleton().string_hash(name,hashValue);
		//calculate the item index in the hash table;
		int hashTableIndex = hashValue.indexHash % m_FileHeader.m_nHashTableSize;
		for(unsigned int k = 0 ; k < m_FileHeader.m_nHashTableSize ; ++k)
		{
			int index = (hashTableIndex + k)%m_FileHeader.m_nHashTableSize;
			if(m_pHashTable[index].m_Flag == HASHFLAG_NOT_USED )
			{
				m_pHashTable[index].m_HashIndex  = hashValue.indexHash ;
				m_pHashTable[index].m_HashValue1 = hashValue.valueA;
				m_pHashTable[index].m_HashValue2 = hashValue.valueB;
				m_pHashTable[index].m_FPIndex    = (int)iDataItem;
				m_pHashTable[index].m_Flag       = HASHFLAG_COMMON;
				entry.m_DataEntryInfo.m_nHashTableIndex = index;
				break;
			}
		}   
	}
}

void CLFilePack::__fit_hash_table()
{
	if((size_t)m_FileHeader.m_nHashTableSize > m_listDataEntry.size() * 2)
		return ;
	__resize_hash_table(m_FileHeader.m_nHashTableSize*2);
}


int CLFilePack::__calDataEntryPoint()
{
	int nStart = __getDataStart();
	for ( size_t i=0; i<m_listDataEntry.size(); i++ )
	{
		sFPDataEntry& entry = m_listDataEntry[i];
		if ( 0 == i )
			entry.m_DataEntryInfo.m_nStart = nStart;
		else
		{
			entry.m_DataEntryInfo.m_nStart = m_listDataEntry[i-1].m_DataEntryInfo.m_nStart + m_listDataEntry[i-1].m_DataEntryInfo.m_nLength;
		}
	}
	return nStart;
}


int CLFilePack::__getDataEntryListStart()
{
	int nStart = sizeof(sFPFileHeader);
	nStart += m_FileHeader.m_nHashTableSize * sizeof(sFPHashTableItem);
	return nStart;
}


int CLFilePack::__getDataStart()
{
	int nStart = sizeof(sFPFileHeader);
	nStart += m_FileHeader.m_nHashTableSize * sizeof(sFPHashTableItem);
	nStart += m_listDataEntry.size() * sizeof(sFPDataEntryInfo);
	return nStart;
}

int  CLFilePack::__find_hash_index(const wchar_t* name)
{
	sHashValue hashValue;
	CLHashDecryptTable::singleton().string_hash(name,hashValue);

	//calculate the item index in the hash table;
	int hashTableIndex = hashValue.indexHash % m_FileHeader.m_nHashTableSize;
	for(unsigned int k = 0 ; k < m_FileHeader.m_nHashTableSize ; ++k)
	{
		int index = (hashTableIndex + k)%m_FileHeader.m_nHashTableSize;
		if(m_pHashTable[index].m_Flag != HASHFLAG_NOT_USED )
		{
			if(m_pHashTable[index].m_HashIndex  == hashValue.indexHash &&
				m_pHashTable[index].m_HashValue1 == hashValue.valueA && 
				m_pHashTable[index].m_HashValue2 == hashValue.valueB )
			{
				///*DataEntry& entry = */m_DataEntrys[ m_pHashTable[index].m_CDIndex ];
				return index;
			}
		}
	} 
	return -1;
}

CLFPInStream* CLFilePack::__unwrapBuffer(int nHashIndex )
{
	if ( -1 == nHashIndex )
		return NULL;
	int nFPIndex = m_pHashTable[nHashIndex].m_FPIndex;
	if ( nFPIndex >= (int)m_listDataEntry.size() )
		return NULL;
	sFPDataEntry& dataEntry = m_listDataEntry[nFPIndex];
	sFPDataEntryInfo &entryInfo = dataEntry.m_DataEntryInfo;
	return __unwrapBuffer( entryInfo );

}

CLFPInStream* CLFilePack::__unwrapBuffer( sFPDataEntryInfo& entryInfo )
{
	if ( lfpdt_dir == entryInfo.m_DataType )
		return NULL;
	CLFPInStream *pStream = new CLFPInStream;

	m_readStream.seekg( entryInfo.m_nStart,ios::beg );

	if ( entryInfo.m_nOriginLength == entryInfo.m_nLength ) /// un compress 
	{
		pStream->m_pData = new char[entryInfo.m_nLength];
		int nLength = entryInfo.m_nLength;
		memset(pStream->m_pData, 0, nLength);
		pStream->m_nLength       = nLength;
		pStream->m_nOriginLength = nLength;
		m_readStream.read( pStream->m_pData, entryInfo.m_nLength);
	}
	else // compress 
	{
		pStream->m_pCompressData = new char[entryInfo.m_nLength];
		int nLength = entryInfo.m_nLength;
		memset(pStream->m_pCompressData, 0, nLength);
		pStream->m_nLength       = nLength;
		pStream->m_nOriginLength = entryInfo.m_nOriginLength;
		m_readStream.read( pStream->m_pCompressData, entryInfo.m_nLength);
		int nRet = pStream->unCompressData();
		if( 0 != nRet )
			return NULL;

	}

	return pStream;
}

void CLFilePack::__fill_file_header()
{
	m_FileHeader.m_sIdentify.m_nBit     = 32;
	m_FileHeader.m_sIdentify.m_nVersion = 1;
	m_FileHeader.m_nHashTableSize = 128;
	m_FileHeader.m_sIdentify.m_IdentifyChar[0] = 'Y';
	m_FileHeader.m_sIdentify.m_IdentifyChar[0] = 'Y';
	m_FileHeader.m_sIdentify.m_IdentifyChar[0] = 'Y';
	m_FileHeader.m_sIdentify.m_IdentifyChar[0] = '\0';


}

bool CLFilePack::__check_file_header()
{
	if(strcmp((const char*)m_FileHeader.m_sIdentify.m_IdentifyChar,"YYY") == 0)
		return true;
	return false;
}